
function EditSelect2 ( oWin, selectCtrl, editCtrl, oCallback /*instance of NestedPropListView */ )
{
	if ( ! oWin )	// probably being called to setup prototype chain
		return this ;

	this.m_oWin = oWin;
	if ( ! (selectCtrl && editCtrl) )
		return null ;
	this.selectCtrl = selectCtrl ;
	this.editCtrl = editCtrl ;
	this.uniqOnly = true ;
	this.m_oCallback = oCallback;
	this.m_itemName = "" ;
	this.m_inSelChangeHandler = false ;
	this.m_syncTextAndValue = true ;
	// default regular expression for validating list names
	this.m_reIllegalChars = /[\."]/ ;
	this.hookEventHandlers();
	this.m_curIndex = -1;
	this.m_lastText = "" ;
	this.m_propChangeSyncTimeoutID = null;
	this.sync();
	return this ;
}
EditSelect2.prototype.hookEventHandlers = function ()
{
	//because of Mozilla Bug 49980 (as of 4/11/01) we can't use anonymous functions
	//as event handlers - so we assign the instance method call generated by
	// getInstanceEventHandler() to a variable of parent window...
	this.m_oWin._ES_onSelChange = getInstanceEventHandler( this, "onSelChange" );
	//... and then hook it in as the ctrl's change handler
	this.origOnSelChangeHandler = addEvent( this.selectCtrl, "change", 
		this.m_oWin._ES_onSelChange );
	this.m_oWin._ES_onEditChange = getInstanceEventHandler( this, "onEditChange" );
	this.origOnEditChangeHandler = addEvent( this.editCtrl, "change", 
		this.m_oWin._ES_onEditChange );
	this.m_oWin._ES_onEditKeyUp = getInstanceEventHandler( this, "onKeyUp" );
	this.origOnKeyUpHandler = addEvent( this.editCtrl, "keyup", 
		this.m_oWin._ES_onEditKeyUp );
	this.m_oWin._ES_onEditKeyPress = getInstanceEventHandler( this, "onKeyPress" );
	this.origOnKeyPressHandler = addEvent( this.editCtrl, "keypress", 
		this.m_oWin._ES_onEditKeyPress );
	if ( isIE5Up )
	{
		this.m_oWin._ES_onEditPropertyChange = getInstanceEventHandler( this, "onEditPropertyChange" );
		this.origOnEditPropertyChangeHandler = addEvent( this.editCtrl, "propertychange", 
			this.m_oWin._ES_onEditPropertyChange );
		// handle crashing rentrance bug in 5.0 but using a timeout and a global func
		if ( ! isIE5_5 )
		{
			// ...set up the timeout handler
			this.m_oWin._doESSyncTextChange = getInstanceEventHandler( this, "syncTextChange" );
		}
	}
	/*
	// chain onchange handlers for the select control and edit controls
	this.origOnSelChangeHandler = this.selectCtrl.onchange ;
	this.selectCtrl.onchange = this.getInstanceEventHandler( "onSelChange" );
	// chain onchange handlers for the select control and edit controls
	this.origOnEditChangeHandler = 	this.editCtrl.onchange ;
	this.editCtrl.onchange = this.getInstanceEventHandler( "onEditChange" );
	// chain onkeyUp handler for the edit control
	this.origOnKeyUpHandler = this.editCtrl.onkeyup ;
	this.editCtrl.onkeyup = this.getInstanceEventHandler( "onKeyUp" );
	this.origOnKeyPressHandler = this.editCtrl.onkeypress ;
	this.editCtrl.onkeypress = this.getInstanceEventHandler( "onKeyPress" );
	if ( isIE5Up )
	{
		// when AutoComplete is used to enter/complete a text entry, onpropertychange is fired
		this.origOnEditPropertyChangeHandler = this.editCtrl.onpropertychange ;
		this.editCtrl.onpropertychange = this.getInstanceEventHandler( "onEditPropertyChange" );
	}
	*/
}
// gets an instance method closure you can assign to an event handler
/*
EditSelect2.prototype.getInstanceEventHandler = function ( handlerName )
{
	function getInstanceMethodClosure( obj )
	{
		// Nav passes the event obj to the handler, pass it along to the real handler
		return function(evt)
		{
			return obj[ handlerName ](evt);		
		}
	}
	return getInstanceMethodClosure( this );
}	// getInstanceEventHandler
*/
EditSelect2.prototype.clear = function ()
{
	// empty the select list and edit control
	this.selectCtrl.options.length = 0;
	setCtrlValue( this.editCtrl, "", true );
	// reset state variables
	this.m_lastText = "" ;
	this.m_curIndex = -1;
}	// clear
EditSelect2.prototype.itemName = function ()
{
	if ( arguments.length )
	{
		this.m_itemName = arguments[ 0 ];
	}
	return this.m_itemName ;
}	// itemName
// sets/gets the regular exp used to validate each char entered into the edit window
EditSelect2.prototype.reIllegalChars = function ()
{
	if ( arguments.length )
	{
		this.reIllegalChars = arguments[ 0 ];
	}
	return this.reIllegalChars ;
}	// itemName
EditSelect2.prototype.sync = function ()
{
	this.m_curIndex = this.selectCtrl.selectedIndex ;

	if ( (this.selectCtrl.options.length == 0) || (this.m_curIndex < 0) )
	{
		this.m_curIndex = -1 ;
		this.m_lastText = "";
	}
	else
	{
		this.m_lastText = getCtrlValue( this.selectCtrl, true );
	}
	setCtrlValue( this.editCtrl, this.m_lastText, true );
}
EditSelect2.prototype.resetItemText = function ( fSelect )
{
	if ( (this.selectCtrl.options.length == 0) || (this.m_curIndex < 0) )
	{
		return ;
	}
	var s = getCtrlDefValue( this.editCtrl );
	this.selectCtrl.options[ this.m_curIndex ].text = s ;
	if ( this.m_syncTextAndValue )
		this.selectCtrl.options[ this.m_curIndex ].value = s ;
	this.m_lastText = s;
	setCtrlValue( this.editCtrl, s );
	this.editCtrl.focus();
	if ( fSelect )
		this.editCtrl.select();
}
EditSelect2.prototype.isDirty = function ()
{
	return isCtrlDirty( this.editCtrl );
}
EditSelect2.prototype.validateChars = function ( s )
{
	if ( this.m_reIllegalChars )
	{
		var pos = s.search( this.m_reIllegalChars );
		if ( pos != -1 )
		{
			var illegalChar = s.charAt( pos );
			var errMsg = 
				( this.m_itemName ?
				"A " + this.m_itemName + " 可能不符合 '" + 
				illegalChar + "' 特性." 
				:
				"一个列表条目可能不符合 '" + illegalChar + 
				"' 特性." );
			return rejectInput( this.editCtrl, errMsg, true );
		}
	}
	return true ;
}
EditSelect2.prototype.validate = function ()
{
	var s = getCtrlValue( this.editCtrl );
	// check for empty string
	if ( "" == s )
	{
		var errMsg = 
			( this.m_itemName ?
			"以下 " + this.m_itemName + " 是必须的." 
			:
			"该字段是必须的." );
		return rejectInput( this.editCtrl, errMsg );
	}
	if ( ! this.validateChars( s ) )
	{
		return false ;
	}
	if ( this.uniqOnly )
	{
		for ( var i = 0; i < this.selectCtrl.length; i++ )
		{
			// don't compare the current item to itself
			if ( (i != this.m_curIndex) && (s == this.selectCtrl.options[ i ].text) )
			{
				if ( this.m_itemName )
				{
					var errMsg = 
						( this.m_itemName ?
						"'" + s + "' 已经被用于 " + 
						this.m_itemName + ". " + this.m_itemName + 
						"必须是唯一的."
						:
						"'" + s + 
						"' 已经是一个列表条目. 列表条目必须是唯一的." );
					return rejectInput( this.editCtrl, errMsg );
				}
			}
		}
	}
	return true ;
}
EditSelect2.prototype.replace = function ()
{
	var selX = this.selectCtrl.selectedIndex ;
	if ( selX == -1 )
	{
		alert( '请先使用"新增"按钮..' );
		return false ;
	}
	var s = getCtrlValue( this.editCtrl );
	if ( (selX != -1) &&
		 (s != "") && 
	     (s != getCtrlValue( this.selectCtrl )) )
	{
		if ( this.validate() )
		{
			this.selectCtrl.options[ selX ].text = s ;
			if ( this.m_syncTextAndValue )
				this.selectCtrl.options[ selX ].value = s ;
			return true ;
		}
	}
	return false ;
}
EditSelect2.prototype.onSelChange = function ()
{
	var fValidChg = true ;
	// confirm actual change in the selected index
	if ( this.selectCtrl.selectedIndex == this.m_curIndex )
		return true ;

	this.m_inSelChangeHandler = true;
	fValidChg = this.doRenameCheck();
	if ( fValidChg )
	{
		if ( ! this.m_oCallback )
		{
			this.sync();
		}
		else
		{
			fValidChg = this.m_oCallback.onESSelChange( this );
			if ( fValidChg )
				this.sync();
		}								   
	}
	if ( ! fValidChg )
	{
		// rollback the list change
		this.selectCtrl.selectedIndex = this.m_curIndex ;
	}
	else
	{
		this.m_curIndex = this.selectCtrl.selectedIndex ;
		if ( this.origOnSelChangeHandler )
		{
			this.origOnSelChangeHandler();
		}
	}
	this.m_inSelChangeHandler = false;
	return fValidChg ;
}
EditSelect2.prototype.onKeyPress = function ( evt )
{

	// validate each character entered
	if ( this.m_reIllegalChars )
	{
		var keyCode ;
		if ( isIE )
		{
			evt = this.m_oWin.event ;
			keyCode = evt.keyCode ;
		}
		else if ( isNav )
		{
			keyCode = evt.which ;
		}
		if ( ! this.validateChars( String.fromCharCode( keyCode ) ) )
		{
			return false ;
		}
	}
	if ( this.origOnKeyPressHandler )
	{
		return this.origOnKeyPressHandler( evt );
	}
	return true ;
}
EditSelect2.prototype.onKeyUp = function ( e )
{

	this.syncTextChange();
	if ( this.origOnKeyUpHandler )
	{
		return this.origOnKeyUpHandler();
	}
	return true ;
}
var inPChg = 0;
EditSelect2.prototype.onEditPropertyChange = function ()
{
	if ( this.m_inSelChangeHandler ) return true;
	if ( this.m_oWin && this.m_oWin.event && 
		 ("value" == this.m_oWin.event.propertyName) )
	{

		if ( isIE5_5 )
		{
			this.syncTextChange();
		}
		else	// handle crashing rentrance bug in 5.0
		{
			if ( ! this.m_propChangeSyncTimeoutID )
			{
				this.m_propChangeSyncTimeoutID = this.m_oWin.setTimeout( "_doESSyncTextChange()", 200 );
			}
		}
	}
	if ( this.origOnEditPropertyChange )
	{
		return this.origOnEditPropertyChange();
	}
	return true ;
}
EditSelect2.prototype.onEditChange = function ()
{

	this.syncTextChange();
	if ( this.origOnEditChange )
	{
		return this.origOnEditChange();
	}
	return true ;
}
EditSelect2.prototype.syncTextChange = function ()
{
	var newText = getCtrlValue( this.editCtrl );

	// update the list with the new item text
	if ( (this.m_curIndex >= 0) && (this.selectCtrl.options[ this.m_curIndex ]) )
	{
		var opt = this.selectCtrl.options[ this.m_curIndex ];
		if ( opt && (opt.text != newText) )
		{
			// Watch out for Mozilla Bug #63681: will cause the select box to GROW (horizontally)!!!
			// Watch out for Mozilla Bug #65996: Will cause the select box to flicker
			// TODO workaround for #63681 - limit size of display text
			opt.text = newText ;
			if ( this.m_syncTextAndValue )
				opt.value = newText ;
		}
	}
	else if ( newText && (newText != this.m_lastText) && 
			  (0 == this.selectCtrl.options.length) )
	{
		if ( this.validate() )
		{
			if ( this.m_oCallback )
			{
				this.m_oCallback.onESAddNew( this );
				// callback is responsible for adding new SELECT OPTION
			}								   
		}
	}
	if ( this.m_propChangeSyncTimeoutID )
		this.m_propChangeSyncTimeoutID = null;
	this.m_lastText = newText ;
}
EditSelect2.prototype.doRenameCheck = function ()
{
	if ( ! isCtrlDirty( this.editCtrl ) )
	{
		return true ;
	}

	var fValidChg = true ;
	// there's a change, validate the new text...
	var newText = getCtrlValue( this.editCtrl );
	if ( ! this.validate() )
	{
		// undo the text change 
		this.resetItemText( true );
		return false ;
	}
	if ( this.m_oCallback && 
		 (! this.m_oCallback.onESRename( getCtrlDefValue( this.editCtrl ), newText )) )
	{
		// undo the text change ????
		this.resetItemText( true );
		return false ;
	}
	// the parent object has accepted the change so update the edit controls default value
	this.editCtrl.defaultValue = newText;
	return true ;
}
EditSelect2.prototype.getEditText = function ()		
{
	return getCtrlValue( this.editCtrl );
}
EditSelect2.prototype.getItemText = function ( index )
{
	if ( index == null )
	{
		index = this.selectCtrl.selectedIndex ;
	}
	if ( index < 0 )
	{
		return "";
	}
	return this.selectCtrl.options[ index ].text ;
}
EditSelect2.prototype.getItemValue = function ( index )
{
	if ( index == null )
	{
		index = this.selectCtrl.selectedIndex ;
	}
	if ( index < 0 )
	{
		return "";
	}
	return this.selectCtrl.options[ index ].value ;
}
EditSelect2.prototype.addUntitled = function ( defTitle, noIncrement, titlePostFix, displayText )
{
	if ( defTitle == null ) defTitle = "Untitled" ;
	if ( ! noIncrement )
	{
		var max = 0 ;
		if ( titlePostFix == null ) 
			titlePostFix = "" ;
		// loop through the list looking for elements with the form 
		// <defTitle>[0-9]+
		var tempA = null ;
		var regExp = new RegExp( "^" + escRegExMetaChars( defTitle ) + "(\\d+)" + 
								   escRegExMetaChars( titlePostFix ) + "$", "i" );
		for ( var i = 0; i < this.selectCtrl.length; i++ )
		{
			tempA = this.selectCtrl.options[ i ].value.match( regExp );
			if ( tempA && parseInt( tempA[ 1 ] ) > max ) max = parseInt( tempA[ 1 ] );
		}
		defTitle += this.getNextUntitledIncrement( max ) + titlePostFix ; 
	}
	if ( !displayText ) 
		displayText = defTitle ;
	// add to list and select
	this.addOption( displayText, defTitle );
	return defTitle ;
}
EditSelect2.prototype.getIncrementedDisplayText = function ( defTextPrefix, incAmount, defTextPostfix )
{
	if ( isNaN( parseInt( incAmount ) ) )
		incAmount = 1;
	if ( defTextPostfix == null ) 
		defTextPostfix = "" ;
	// loop through the list looking for elements with the form 
	// <defTextPrefix>[0-9]+
	var tempA = null ;
	var regExp = new RegExp( "^" + escRegExMetaChars( defTextPrefix ) + "(\\d+)" + 
							       escRegExMetaChars( defTextPostfix ) + "$", "i" );
	var max = 0 ;
	for ( var i = 0; i < this.selectCtrl.length; i++ )
	{
		tempA = this.selectCtrl.options[ i ].text.match( regExp );
		if ( tempA && parseInt( tempA[ 1 ] ) > max ) max = parseInt( tempA[ 1 ] );
	}
	return defTextPrefix + (max + incAmount) + defTextPostfix ; 
}
EditSelect2.prototype.getNextUntitledIncrement = function ( curMax )
{
	return curMax + 1 ;
}
EditSelect2.prototype.remove = function ()
{
	selectDel( this.selectCtrl );
	this.sync();
	return true ;
}
EditSelect2.prototype.count = function ()
{
	return ( this.selectCtrl ? this.selectCtrl.length : 0 );
}
EditSelect2.prototype.addOption = function ( sText, 
											sValue /* optional, uses sText if missing*/, 
											fNoEditFocus /* optional, gives edit ctrl focus by default */ )
{
	// add to list and select
	var len	= this.selectCtrl.length ;
	if ( (sValue == "") || (sValue == null) )
	{
		sValue = sText ;
	}

	selAddOption( this.selectCtrl, sText, sValue, true );
	this.sync();
	// automatically set the focus to the edit ctrl and select the text
	// unless overridden
	if( ! fNoEditFocus )
	{
		this.editCtrl.focus();
		this.editCtrl.select();
	}
}	// addOption

